/// File: VSLabCore\toolwindow.fs
/// 
/// Author: Antonio Cisternino (cisterni@di.unipi.it)
/// Author: Davide Morelli (morellid@di.unipi.it)
///   
/// ------------------------------------------------------------
/// Copyright (c) Microsoft Corporation.  All rights reserved.
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------

module VSLabCore

open System
open System.Windows.Forms
open System.Drawing
open System.Runtime.InteropServices
open System.Net
open System.Net.Sockets
open System.Runtime.Serialization.Formatters.Binary
open System.Threading

open EnvDTE
open EnvDTE80
open EnvDTE90
open Extensibility;;

/// <summary>
/// The enumeration containing the WM_ messages used to proxy the Viewlets.
/// </summary>
type Messages =
  | WM_MOVE = 0x03
  | WM_MOUSEMOVE = 0x0200
  | WM_LBUTTONDOWN = 0x0201
  | WM_LBUTTONUP = 0x0202
  | WM_LBUTTONDBLCLK = 0x0203
  | WM_RBUTTONDOWN = 0x0204
  | WM_RBUTTONUP = 0x0205
  | WM_RBUTTONDBLCLK = 0x0206
  | WM_MBUTTONDOWN = 0x0207
  | WM_MBUTTONUP = 0x0208
  | WM_MBUTTONDBLCLK = 0x0209
  | WM_MOUSEWHEEL = 0x020A // if winnt > 6 is marked 0x020e
  | WM_XBUTTONDOWN = 0x020B
  | WM_XBUTTONUP = 0x020C
  | WM_XBUTTONDBLCLK = 0x020D
  | WM_MOUSEHOVER = 0x02A1
  | WM_MOUSELEAVE = 0x02A3
  | WM_PAINT = 0x000F
  | WM_ERASEBKGND = 0x0014
  | WM_SHOWWINDOW = 0x18
  | WM_CHILDACTIVATE = 0x22
  | WM_SETFOCUS = 0x07
  | WM_KILLFOCUS = 0x08
  | WM_APP = 0x8000
  | WM_APP_PAINT = 0x8001
  | WM_APP_ERASEBKGND = 0x8002
  | WM_APP_VISIBLECHANGED = 0x8003
  | WM_APP_SHOWCARET = 0x8004
  | WM_APP_CARETPOS = 0x8005
  | WM_APP_CARETSZ = 0x8006
  | WM_APP_SETCURSOR = 0x8007
  | WM_SIZE = 0x5
  | WM_KEYDOWN = 0x0100
  | WM_KEYUP = 0x0101
  | WM_CHAR = 0x102
  

[<DllImport("user32.dll")>]
extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam)  

[<DllImport("user32.dll", ExactSpelling=true, CharSet=CharSet.Auto)>]
extern IntPtr GetParent(IntPtr hWnd)

[<DllImport("user32.dll")>]
extern int CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight)

[<DllImport("user32.dll")>]
extern int DestroyCaret()

[<DllImport("user32.dll")>]
extern UInt32 GetCaretBlinkTime()

[<DllImport("user32.dll")>]
extern int GetCaretPos(Point* lpPoint)

[<DllImport("user32.dll")>]
extern int HideCaret(IntPtr hWnd)

[<DllImport("user32.dll")>]
extern int SetCaretBlinkTime(UInt32 uMSeconds)

[<DllImport("user32.dll")>]
extern int SetCaretPos(int X, int Y)

[<DllImport("user32.dll")>]
extern int ShowCaret(IntPtr hWnd)

/// <summary>
/// A class used by ViewletToolWindow to detect when the tool window gets closed
/// </summary>
type internal ToolWindowSubclass() as x =
  inherit NativeWindow()
  
  let evt_fire, evt_listen = Event.create()
  
  /// <summary>
  /// The Activation event.
  /// </summary>
  member x.Activated = evt_listen
  
  /// <summary>
  /// The Event Loop
  /// </summary>
  /// <param name="m"> The WM_ Message to be processed </param>
  override x.WndProc(m) =
    if (enum(m.Msg) = Messages.WM_SHOWWINDOW) then
      evt_fire(false)
    
    base.WndProc(&m)
  

/// <summary>
/// The class used by Visual Studio to create a Tool Window.
/// Visual Studio creates an instance of this class for every tool window.
/// This class communicates with the Viewlet class (in the F# Interactive process),
/// the mouse, keyboard and visibility events are sent to the Viewlet class,
/// the graphical context is rendered in the Vielwet class.
/// </summary>
type ViewletToolWindow() as x =
  inherit UserControl()
  
  let mutable target = IntPtr.Zero
  let mutable clip = new Rectangle()
  let subclass = new ToolWindowSubclass()
  let mutable parent = IntPtr.Zero
  let mutable visible = true
  let mutable caretVisible = false
  let mutable caretSize = new Size()
  let mutable caretPosition = new Point()
  
  let mutable mousePosition = System.IntPtr.Zero
  
  //do x.SetStyle(ControlStyles.AllPaintingInWmPaint ||| ControlStyles.DoubleBuffer, true)
  do x.SetStyle(ControlStyles.AllPaintingInWmPaint, true)
  
  /// <summary>
  /// The windows handle has been created.
  /// This method sends the handle to the Viewlet instance
  /// and receives the Viewlet handle
  /// </summary>
  /// <param name="e"> unused argument </param>
  override x.OnHandleCreated(e) =
    parent <- GetParent(x.Handle)
    subclass.AssignHandle(parent)
    subclass.Activated.Add(fun v ->
      visible <- false
      if target <> IntPtr.Zero then
        SendMessage(target, (int Messages.WM_APP_VISIBLECHANGED), IntPtr.Zero, IntPtr.Zero) |> ignore
    )
    // connect socket
    let tcpClient = new TcpClient("127.0.0.1", 13000)
    let stream = tcpClient.GetStream()
    let formatter = new BinaryFormatter()
    // read HWND
    target <- unbox<IntPtr> (formatter.Deserialize(stream))
    // Read String (id and type)
    // TODO
    // send HWND
    formatter.Serialize(stream, x.Handle)
    // send GUID
    // TODO
    // close
    stream.Close()
    tcpClient.Close()
    ()
    
  /// <summary>
  /// Visual Studio has invoked the Paint Event.
  /// The message is being forwarded to the Viewlet which will take care of drawing in the tool window canvas.
  /// </summary>
  /// <param name="g"> the Paint Event Args containing the Clip Rectangle to draw </param>
  override x.OnPaint(g) = 
    if not visible then
      visible <- true
      SendMessage(target, int(Messages.WM_APP_VISIBLECHANGED), new IntPtr(1), IntPtr.Zero) |> ignore
    clip <- g.ClipRectangle
  
  /// <summary>
  /// Visual Studio has invoked the PaintBackground Event.
  /// The message is being forwarded to the Viewlet which will take care of drawing in the tool window canvas.
  /// </summary>
  /// <param name="g"> the Paint Event Args containing the Clip Rectangle to draw </param>
  override x.OnPaintBackground(g) =
    clip <- g.ClipRectangle
    
  override x.OnGotFocus(e) =
    if caretVisible then
      CreateCaret(x.Handle, IntPtr.Zero, caretSize.Width, caretSize.Height) |> ignore
      SetCaretPos(caretPosition.X, caretPosition.Y) |> ignore
      ShowCaret(x.Handle) |> ignore
  
  override x.OnLostFocus(e) =
    if caretVisible then
      DestroyCaret() |> ignore
      
  member x.UpdateCaretSize (s) =
    caretSize <- s
    if x.Focused then
      DestroyCaret() |> ignore
      CreateCaret(x.Handle, IntPtr.Zero, caretSize.Width, caretSize.Height) |> ignore
      ShowCaret(x.Handle) |> ignore
    
  
  /// <summary>
  /// The Event Loop.
  /// The messages are being forwarded to the Viewlet 
  /// </summary>
  /// <param name="m"> the WM_ Message to process</param>
  override x.WndProc(m) =
    base.WndProc(&m)

    if target <> IntPtr.Zero then
      if Enum.IsDefined(typeof<Messages>, m.Msg) then
        System.Diagnostics.Debug.WriteLineIf(enum(m.Msg) = Messages.WM_MOUSEMOVE, "Message: " + (m.Msg.ToString()))
        match enum(int(m.Msg)) with
        | Messages.WM_MOUSEMOVE ->
          if mousePosition <> m.LParam then
            mousePosition <- m.LParam
            SendMessage(target, m.Msg, m.WParam, m.LParam) |> ignore
        | Messages.WM_PAINT ->
          let encode a b = (a <<< 16) ||| (b &&& 0xFFFF)
          let lt = encode clip.Left clip.Top
          let wh = encode clip.Width clip.Height
          SendMessage(target, int(Messages.WM_APP_PAINT), new IntPtr(lt), new IntPtr(wh)) |> ignore
        | Messages.WM_ERASEBKGND ->
          let encode a b = (a <<< 16) ||| (b &&& 0xFFFF)
          let lt = encode clip.Left clip.Top
          let wh = encode clip.Width clip.Height
          SendMessage(target, int(Messages.WM_APP_ERASEBKGND), new IntPtr(lt), new IntPtr(wh)) |> ignore
        | Messages.WM_APP_SHOWCARET ->
          match (int m.WParam) with
          | 0 -> caretVisible <- true
          | 1 -> caretVisible <- false
          | 2 -> ShowCaret(x.Handle) |> ignore
          | 3 -> HideCaret(x.Handle) |> ignore
          | _ -> ()
        | Messages.WM_APP_CARETPOS ->
          caretPosition <- new Point((int m.WParam), (int m.LParam))
          SetCaretPos(caretPosition.X, caretPosition.Y) |> ignore
        | Messages.WM_APP_CARETSZ ->
          x.UpdateCaretSize(new Size((int m.WParam), (int m.LParam)))
        | Messages.WM_APP_SETCURSOR ->
          // FIXME: This seem not working
          x.Cursor <- new Cursor(m.WParam)
        | _ ->
          SendMessage(target, m.Msg, m.WParam, m.LParam) |> ignore

  interface IDisposable with
    /// <summary>
    /// The tool window is being closed and all resources disposed.
    /// </summary>
    member x.Dispose() =
      if parent <> IntPtr.Zero then
        subclass.ReleaseHandle()

    